home *** CD-ROM | disk | FTP | other *** search
- """
- This module provides widgets to use aptdaemon in a GTK application.
- """
- # Copyright (C) 2008-2009 Sebastian Heinlein <devel@glatzor.de>
- #
- # Licensed under the GNU General Public License Version 2
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-
- __author__ = "Sebastian Heinlein <devel@glatzor.de>"
-
- from gettext import gettext as _
- import os
- import pty
-
- import apt_pkg
- import dbus
- import dbus.mainloop.glib
- import gobject
- import gtk
- import pango
- import pygtk
- import vte
-
- import client
- from enums import *
-
- dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
-
- (COLUMN_ID,
- COLUMN_PACKAGE) = range(2)
-
-
- class AptStatusIcon(gtk.Image):
- """
- Provides a gtk.Image which shows an icon representing the status of a
- aptdaemon transaction
- """
- def __init__(self, transaction=None, size=gtk.ICON_SIZE_DIALOG):
- gtk.Image.__init__(self)
- self.icon_size = size
- self.icon_name = None
- self._signals = []
- self.set_alignment(0, 0)
- if transaction != None:
- self.set_transaction(transaction)
-
- def set_transaction(self, transaction):
- """Connect to the given transaction"""
- for sig in self._signals:
- gobject.source_remove(sig)
- self._signals = []
- self._signals.append(
- transaction.connect("status", self._on_status_changed))
-
- def set_icon_size(self, size):
- """Set the icon size to gtk stock icon size value"""
- self.icon_size = size
-
- def _on_status_changed(self, transaction, status):
- """Set the status icon according to the changed status"""
- icon_name = get_status_icon_name_from_enum(status)
- if icon_name is None:
- icon_name = gtk.STOCK_MISSING_IMAGE
- if icon_name != self.icon_name:
- self.set_from_icon_name(icon_name, self.icon_size)
- self.icon_name = icon_name
-
-
- class AptRoleIcon(AptStatusIcon):
- """
- Provides a gtk.Image which shows an icon representing the role of an
- aptdaemon transaction
- """
- def set_transaction(self, transaction):
- for sig in self._signals:
- gobject.source_remove(sig)
- self._signals = []
- self._signals.append(
- transaction.connect("role", self._on_role_changed))
-
- def _on_role_changed(self, transaction, role_enum):
- """Show an icon representing the role"""
- icon_name = get_role_icon_name_from_enum(role_enum)
- if icon_name is None:
- icon_name = gtk.STOCK_MISSING_IMAGE
- if icon_name != self.icon_name:
- self.set_from_icon_name(icon_name, self.icon_size)
- self.icon_name = icon_name
-
-
- class AptStatusAnimation(AptStatusIcon):
- """
- Provides a gtk.Image which shows an animation representing the
- transaction status
- """
- def __init__(self, transaction=None, size=gtk.ICON_SIZE_DIALOG):
- AptStatusIcon.__init__(self, transaction, size)
- self.animation = []
- self.ticker = 0
- self.frame_counter = 0
- self.iter = 0
- name = get_status_animation_name_from_enum(STATUS_WAITING)
- fallback = get_status_icon_name_from_enum(STATUS_WAITING)
- self.set_animation(name, fallback)
-
- def set_animation(self, name, fallback=None, size=None):
- """Show and start the animation of the given name and size"""
- if name == self.icon_name:
- return
- if size is not None:
- self.icon_size = size
- self.stop_animation()
- animation = []
- (width, height) = gtk.icon_size_lookup(self.icon_size)
- theme = gtk.icon_theme_get_default()
- if name is not None and theme.has_icon(name):
- pixbuf = theme.load_icon(name, width, 0)
- rows = pixbuf.get_height() / height
- cols = pixbuf.get_width() / width
- for r in range(rows):
- for c in range(cols):
- animation.append(pixbuf.subpixbuf(c * width, r * height,
- width, height))
- if len(animation) > 0:
- self.animation = animation
- self.iter = 0
- self.set_from_pixbuf(self.animation[0])
- self.start_animation()
- else:
- self.set_from_pixbuf(pixbuf)
- self.icon_name = name
- elif fallback is not None and theme.has_icon(fallback):
- self.set_from_icon_name(fallback, self.icon_size)
- self.icon_name = fallback
- else:
- self.set_from_icon_name(gtk.STOCK_MISSING_IMAGE)
-
- def start_animation(self):
- """Start the animation"""
- if self.ticker == 0:
- self.ticker = gobject.timeout_add(200, self._advance)
-
- def stop_animation(self):
- """Stop the animation"""
- if self.ticker != 0:
- gobject.source_remove(self.ticker)
- self.ticker = 0
-
- def _advance(self):
- """
- Show the next frame of the animation and stop the animation if the
- widget is no longer visible
- """
- if self.get_property("visible") == False:
- self.ticker = 0
- return False
- self.iter = self.iter + 1
- if self.iter >= len(self.animation):
- self.iter = 0
- self.set_from_pixbuf(self.animation[self.iter])
- return True
-
- def _on_status_changed(self, transaction, status):
- """
- Set the animation according to the changed status
- """
- name = get_status_animation_name_from_enum(status)
- fallback = get_status_icon_name_from_enum(status)
- self.set_animation(name, fallback)
-
-
- class AptRoleLabel(gtk.Label):
- """
- Status label for the running aptdaemon transaction
- """
- def __init__(self, transaction=None):
- gtk.Label.__init__(self)
- self.set_alignment(0, 0)
- self.set_ellipsize(pango.ELLIPSIZE_END)
- self._signals = []
- if transaction != None:
- self.set_transaction(transaction)
-
- def set_transaction(self, transaction):
- """Connect the status label to the given aptdaemon transaction"""
- for sig in self._signals:
- gobject.source_remove(sig)
- self._signals = []
- self._signals.append(transaction.connect("role", self._on_role_changed))
-
- def _on_role_changed(self, transaction, role):
- """Set the role text."""
- self.set_markup(get_role_localised_present_from_enum(role))
-
-
- class AptStatusLabel(gtk.Label):
- """
- Status label for the running aptdaemon transaction
- """
- def __init__(self, transaction=None):
- gtk.Label.__init__(self)
- self.set_alignment(0, 0)
- self.set_ellipsize(pango.ELLIPSIZE_END)
- self._signals = []
- if transaction != None:
- self.set_transaction(transaction)
-
- def set_transaction(self, transaction):
- """Connect the status label to the given aptdaemon transaction"""
- for sig in self._signals:
- gobject.source_remove(sig)
- self._signals = []
- self._signals.append(
- transaction.connect("status", self._on_status_changed))
- self._signals.append(
- transaction.connect("status-details",
- self._on_status_details_changed))
-
- def _on_status_changed(self, transaction, status):
- """Set the status text according to the changed status"""
- self.set_markup("<i>%s</i>" % get_status_string_from_enum(status))
-
- def _on_status_details_changed(self, transaction, text):
- """Set the status text to the one reported by apt"""
- self.set_markup("<i>%s</i>" % text)
-
-
- class AptProgressBar(gtk.ProgressBar):
- """
- Provides a gtk.Progress which represents the progress of an aptdaemon
- transactions
- """
- def __init__(self, transaction=None):
- gtk.ProgressBar.__init__(self)
- self.set_ellipsize(pango.ELLIPSIZE_END)
- self.set_text(" ")
- self.set_pulse_step(0.05)
- self._signals = []
- if transaction != None:
- self.set_transaction(transaction)
-
- def set_transaction(self, transaction):
- """Connect the progress bar to the given aptdaemon transaction"""
- for sig in self._signals:
- gobject.source_remove(sig)
- self._signals = []
- self._signals.append(
- transaction.connect("finished", self._on_finished))
- self._signals.append(
- transaction.connect("progress", self._on_progress_changed))
- self._signals.append(
- transaction.connect("progress-details", self._on_progress_details))
-
- def _on_progress_changed(self, transaction, progress):
- """
- Update the progress according to the latest progress information
- """
- if progress > 100:
- self.pulse()
- else:
- self.set_fraction(progress/100.0)
-
- def _on_progress_details(self, transaction, items_done, items_total,
- bytes_done, bytes_total, speed, eta):
- """
- Update the progress bar text according to the latest progress details
- """
- if items_total == 0 and bytes_total == 0:
- self.set_text(" ")
- return
- if speed != 0:
- self.set_text(_("Downloaded %sB of %sB "
- "at %sB/s") % (apt_pkg.SizeToStr(bytes_done),
- apt_pkg.SizeToStr(bytes_total),
- apt_pkg.SizeToStr(speed)))
- else:
- self.set_text(_("Downloaded %sB "
- "of %sB") % (apt_pkg.SizeToStr(bytes_done),
- apt_pkg.SizeToStr(bytes_total)))
-
- def _on_finished(self, transaction, exit):
- """Set the progress to 100% when the transaction is complete"""
- self.set_fraction(1)
-
- class AptTerminalExpander(gtk.Expander):
- def __init__(self, transaction=None):
- gtk.Expander.__init__(self, _("Details"))
- self._signals = []
- self.set_sensitive(False)
- self.set_expanded(False)
- self.terminal = AptTerminal()
- self.add(self.terminal)
- if transaction != None:
- self.set_transaction(transaction)
-
- def set_transaction(self, transaction):
- """Connect the status label to the given aptdaemon transaction"""
- for sig in self._signals:
- gobject.source_remove(sig)
- self._signals.append(
- transaction.connect("allow_terminal",
- self._on_allow_terminal))
- self.terminal.set_transaction(transaction)
-
- def _on_allow_terminal(self, transaction, allow_terminal):
- """
- Connect the terminal to the pty device
- """
- if allow_terminal == True:
- self.set_sensitive(True)
-
- class AptTerminal(vte.Terminal):
- def __init__(self, transaction=None):
- vte.Terminal.__init__(self)
- self._signals = []
- self._master, self._slave = pty.openpty()
- self._ttyname = os.ttyname(self._slave)
- self.set_size(80, 24)
- self.set_pty(self._master)
- if transaction != None:
- self.set_transaction(transaction)
-
- def set_transaction(self, transaction):
- """Connect the status label to the given aptdaemon transaction"""
- for sig in self._signals:
- gobject.source_remove(sig)
- self._signals.append(
- transaction.connect("allow-terminal",
- self._on_allow_terminal))
- self._transaction = transaction
- self._transaction.set_terminal(self._ttyname)
-
- def _on_allow_terminal(self, transaction, allow_terminal):
- """
- Show the terminal
- """
- self.set_sensitive(allow_terminal)
-
- class AptCancelButton(gtk.Button):
- """
- Provides a gtk.Button which allows to cancel a running aptdaemon
- transaction
- """
- def __init__(self, transaction):
- gtk.Button.__init__(self, stock=gtk.STOCK_CANCEL)
- self.set_sensitive(True)
- self._signals = []
- if transaction != None:
- self.set_transaction(transaction)
-
- def set_transaction(self, transaction):
- """Connect the status label to the given aptdaemon transaction"""
- for sig in self._signals:
- gobject.source_remove(sig)
- self._signals = []
- self._signals.append(
- transaction.connect("finished", self._on_finished))
- self._signals.append(
- transaction.connect("allow-cancel",
- self._on_allow_cancel_changed))
- self.connect("clicked", self._on_clicked, transaction)
-
- def _on_allow_cancel_changed(self, transaction, allow_cancel):
- """
- Enable the button if cancel is allowed and disable it in the other case
- """
- self.set_sensitive(allow_cancel)
-
- def _on_finished(self, transaction, status):
- self.set_sensitive(False)
-
- def _on_clicked(self, button, transaction):
- transaction.cancel()
- self.set_sensitive(False)
-
-
- class AptProgressDialog(gtk.Dialog):
- """
- Complete progress dialog for long taking aptdaemon transactions, which
- features a progress bar, cancel button, status icon and label
- """
- def __init__(self, transaction=None, parent=None, terminal=True,
- debconf=True):
- gtk.Dialog.__init__(self, buttons=None, parent=parent)
- self.debconf = debconf
- # Setup the dialog
- self.set_border_width(6)
- self.set_has_separator(False)
- #self.set_resizable(False)
- self.vbox.set_spacing(6)
- # Setup the cancel button
- self.button_cancel = AptCancelButton(transaction)
- self.action_area.pack_start(self.button_cancel, False, False, 0)
- # Setup the status icon, label and progressbar
- hbox = gtk.HBox()
- hbox.set_spacing(12)
- hbox.set_border_width(6)
- self.icon = AptRoleIcon()
- hbox.pack_start(self.icon, False, True, 0)
- vbox = gtk.VBox()
- vbox.set_spacing(12)
- self.label_role = gtk.Label()
- self.label_role.set_alignment(0, 0)
- vbox.pack_start(self.label_role, False, True, 0)
- vbox_progress = gtk.VBox()
- vbox_progress.set_spacing(6)
- self.progress = AptProgressBar()
- vbox_progress.pack_start(self.progress, False, True, 0)
- self.label = AptStatusLabel()
- self.label._on_status_changed(None, STATUS_WAITING)
- vbox_progress.pack_start(self.label, False, True, 0)
- vbox.pack_start(vbox_progress, False, True, 0)
- hbox.pack_start(vbox, True, True, 0)
- if terminal == True:
- self.expander = AptTerminalExpander()
- vbox.pack_start(self.expander, False, True, 0)
- self.vbox.pack_start(hbox, True, True, 0)
- self._transaction = None
- self._signals = []
- self.set_title("")
- self.realize()
- self.progress.set_size_request(350, -1)
- self.window.set_functions(gtk.gdk.FUNC_MOVE|gtk.gdk.FUNC_RESIZE)
- if transaction != None:
- self.set_transaction(transaction)
- self._running = False
-
- def run(self, attach=False):
- """Run the transaction and show the progress in the dialog.
-
- Keyword argument:
- attach -- do not start the transaction but instead only monitor
- an already running one
- """
- parent = self.get_transient_for()
- if attach:
- self._transaction.attach(error_handler=self._on_error,
- reply_handler=self._on_run)
- else:
- self._transaction.run(error_handler=self._on_error,
- reply_handler=self._on_run)
- #FIXME: Evil woraround to emulate the blocking behavior of a dialog
- # without making use of a nested main loop. Only the default
- # main loop receives D-Bus signals.
- self._running = True
- while self._running:
- gobject.main_context_default().iteration()
- return self._transaction._exit
-
- def _on_error(self, error):
- """Stop the "emulated" loop of the progress dialog."""
- self._running = False
- if error.get_dbus_name() != \
- "org.freedesktop.PolicyKit.Error.NotAuthorized":
- raise error
-
- def _on_run(self):
- """Show the dialog."""
- self.show_all()
-
- def _on_role(self, transaction, role_enum):
- """Show the role of the transaction in the dialog interface"""
- role = get_role_localised_present_from_enum(role_enum)
- self.set_title(role)
- self.label_role.set_markup("<big><b>%s</b></big>" % role)
-
- def _setup_http_proxy(self, transaction):
- try:
- import gconf
- except ImportError:
- return
- client = gconf.client_get_default()
- if client.get_bool("/system/http_proxy/use_http_proxy"):
- host = client.get_string("/system/http_proxy/host")
- port = client.get_int("/system/http_proxy/port")
- transaction.set_http_proxy("http://%s:%s/" % (host, port))
-
- def set_transaction(self, transaction):
- """Connect the dialog to the given aptdaemon transaction"""
- for sig in self._signals:
- gobject.source_remove(sig)
- self._signals = []
- self._signals.append(
- transaction.connect("role", self._on_role))
- self._signals.append(
- transaction.connect("medium-required", self._on_medium_required))
- self._signals.append(
- transaction.connect("config-file-prompt", self._on_config_file_prompt))
- self._signals.append(
- transaction.connect("finished", self._on_finished))
- self.progress.set_transaction(transaction)
- self.icon.set_transaction(transaction)
- self.label.set_transaction(transaction)
- if hasattr(self, "expander"):
- self.expander.set_transaction(transaction)
- self._transaction = transaction
- if self.debconf:
- self._transaction.set_debconf_frontend("gnome")
- self._setup_http_proxy(transaction)
-
- def _on_medium_required(self, transaction, medium, drive):
- dialog = AptMediumRequiredDialog(medium, drive, self)
- res = dialog.run()
- dialog.hide()
- if res == gtk.RESPONSE_OK:
- self._transaction.provide_medium(medium)
- else:
- self._transaction.cancel()
-
- def _on_config_file_prompt(self, transaction, old, new):
- dialog = AptConfigFilePromptDialog(old, new, self)
- res = dialog.run()
- dialog.hide()
- if res == gtk.RESPONSE_YES:
- self._transaction.config_file_prompt_answer(old, "keep")
- else:
- self._transaction.config_file_prompt_answer(old, "replace")
-
- def _on_finished(self, transaction, status):
- self._running = False
-
- class AptMediumRequiredDialog(gtk.MessageDialog):
- def __init__(self, medium, drive, parent=None):
- gtk.MessageDialog.__init__(self, parent=parent,
- type=gtk.MESSAGE_INFO,
- buttons=gtk.BUTTONS_OK_CANCEL)
- #TRANSLATORS: %s represents the name of a CD or DVD
- text = _("CD/DVD '%s' is required") % medium
- #TRANSLATORS: %s is the name of the CD/DVD drive
- desc = _("Please insert the above CD/DVD into the drive '%s' to "
- "install software packages from the medium.") % drive
- self.set_markup("<big><b>%s</b></big>\n\n%s" % (text, desc))
-
- class AptConfigFilePromptDialog(gtk.MessageDialog):
- def __init__(self, old, new, parent=None):
- gtk.MessageDialog.__init__(self, parent=parent,
- type=gtk.MESSAGE_INFO)
- self.add_buttons(_("_Replace"), gtk.RESPONSE_YES,
- _("_Keep"), gtk.RESPONSE_NO)
- self.set_default_response(gtk.RESPONSE_NO)
- # FIMXE: use better buttons, use better text
- text = _("Configuration file '%s' changed") % old
- desc = _("Do you want to use the new version?")
- self.set_markup("<big><b>%s</b></big>\n\n%s" % (text, desc))
-
- class AptMessageDialog(gtk.MessageDialog):
- """
- Dialog for aptdaemon messages with details in an expandable text view
- """
- def __init__(self, enum, details=None, parent=None):
- gtk.MessageDialog.__init__(self, parent=parent,
- type=gtk.MESSAGE_INFO,
- buttons=gtk.BUTTONS_CLOSE)
- text = get_msg_string_from_enum(enum)
- desc = get_msg_description_from_enum(enum)
- self.set_markup("<big><b>%s</b></big>\n\n%s" % (text, desc))
- self.set_details(details)
-
- def set_details(self, details):
- if details == "":
- return
- #TRANSLATORS: expander label in the error dialog
- expander = gtk.expander_new_with_mnemonic(_("_Details"))
- expander.set_spacing(6)
- scrolled = gtk.ScrolledWindow()
- scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrolled.set_shadow_type(gtk.SHADOW_ETCHED_IN)
- textview = gtk.TextView()
- buffer = textview.get_buffer()
- buffer.insert_at_cursor(details)
- scrolled.add(textview)
- expander.add(scrolled)
- box = self.label.get_parent()
- box.add(expander)
- expander.show_all()
-
-
- class AptErrorDialog(AptMessageDialog):
- """
- Dialog for aptdaemon errors with details in an expandable text view
- """
- def __init__(self, error=None, parent=None):
- gtk.MessageDialog.__init__(self, parent=parent,
- type=gtk.MESSAGE_ERROR,
- buttons=gtk.BUTTONS_CLOSE)
- text = get_error_string_from_enum(error.code)
- desc = get_error_description_from_enum(error.code)
- self.set_markup("<big><b>%s</b></big>\n\n%s" % (text, desc))
- self.set_details(error.details)
-